<script lang="ts" setup>
import { getViewer } from "@/utils";
import {
  Cartesian2,
  Cartesian3,
  Cartographic,
  Math as CesiumMath,
  SceneMode,
  SceneTransforms,
  ScreenSpaceEventHandler,
  ScreenSpaceEventType,
  Viewer,
} from "mars3d-cesium";
import { computed, reactive, ref, type StyleValue } from "vue";
import VXETable from "vxe-table";
import IconDownload from "~icons/icon-park/download-web";
import IconTree from "~icons/noto/evergreen-tree";
import IconPick from "~icons/noto/pick";
import IconShot from "~icons/twemoji/cut-of-meat";

const state = reactive<{ cg1?: Cartographic; cg2?: Cartographic; cg3?: Cartographic; data?: string }>({});
const n = ref(12);
const style = computed<StyleValue>(() => layoutProportion(n.value));
const viewers: Viewer[] = [];
const poles: Cartesian3[] = [Cartesian3.fromDegrees(0, 90, 15000000), Cartesian3.fromDegrees(0, -90, 15000000)];
const mainRef = ref<HTMLDivElement>();
const { floor, sqrt, pow, round, ceil } = Math;
const { toDegrees: deg } = CesiumMath;

function create() {
  destory();
  const arc = 360 / (n.value - 2);

  for (let i = 1; i <= n.value; i++) {
    const viewer = getViewer("container" + i, {
      sceneMode: SceneMode.SCENE3D,
      shouldAnimate: true,
      contextOptions: { webgl: { preserveDrawingBuffer: true } },
    });
    let destination: Cartesian3;

    if (n.value - i > 1) {
      destination = Cartesian3.fromDegrees(arc * (i - 1), 0, 15e6);
    } else {
      destination = poles[n.value - i];
    }

    viewer.camera.flyTo({ destination, duration: 5 });
    viewer.scene.globe.enableLighting = true;

    new ScreenSpaceEventHandler(viewer.canvas).setInputAction(
      <ScreenSpaceEventHandler.MotionEventCallback>((ev) => handleMotionEvent(viewer, ev)),
      ScreenSpaceEventType.MOUSE_MOVE,
    );

    viewers.push(viewer);
  }
}

function destory() {
  for (const viewer of viewers) {
    if (!viewer.isDestroyed()) viewer.destroy();
  }
  viewers.length = 0;
}

function handleMotionEvent(viewer: Viewer, ev: ScreenSpaceEventHandler.MotionEvent) {
  const c2 = ev.endPosition;
  const ct1 = screen2coordinate(viewer, c2);
  const ct2 = screen2coordinate(viewer, c2, "terrain");
  const ct3 = screen2coordinate(viewer, c2, "scene");
  state.cg1 = ct1.cg;
  state.cg2 = ct2.cg;
  state.cg3 = ct3.cg;
}

function handleMouseMove(viewer: Viewer, ev: MouseEvent) {
  const c2 = new Cartesian2(ev.clientX, ev.clientY);
  const ct1 = screen2coordinate(viewer, c2);
  const ct2 = screen2coordinate(viewer, c2, "terrain");
  const ct3 = screen2coordinate(viewer, c2, "scene");
  state.cg1 = ct1.cg;
  state.cg2 = ct2.cg;
  state.cg3 = ct3.cg;
}

function screen2coordinate(viewer: Viewer, screen: Cartesian2, mode: "ellipsoid" | "scene" | "terrain" = "ellipsoid") {
  let c3: Cartesian3 | undefined;
  let cg: Cartographic | undefined;
  let ray = viewer.camera.getPickRay(screen);

  switch (mode) {
    case "scene":
      c3 = viewer.scene.pickPosition(screen);
      break;
    case "terrain":
      ray && (c3 = viewer.scene.globe.pick(ray, viewer.scene));
      break;
    case "ellipsoid":
      c3 = viewer.camera.pickEllipsoid(screen);
  }

  if (c3) {
    cg = Cartographic.fromCartesian(c3);
  }

  return { c3, cg };
}

function coordinate2screen(viewer: Viewer, coordinate: Cartesian3) {
  return SceneTransforms.worldToWindowCoordinates(viewer.scene, coordinate);
}

function toMessage(cg: Cartographic | undefined) {
  if (cg) {
    return deg(cg.longitude).toFixed(6) + ", " + deg(cg.latitude).toFixed(6);
  } else {
    return "-, -";
  }
}

function snapshot() {
  if (!viewers.length) return;
  const viewer = viewers[0];
  state.data = viewer.canvas.toDataURL("image/png");
}

function download() {
  if (!viewers.length) return;
  const viewer = viewers[0];
  viewer.canvas.toBlob((blob) => {
    if (blob) VXETable.saveFile({ filename: "a", type: "png", content: blob });
  }, "image/png");
}

function click2hide() {
  state.data = undefined;
}

// 等比布局
function layoutProportion(ln: number) {
  if (ln <= 1) return { width: "100%", height: "100%" };

  const sqrted = sqrt(ln);
  const base = floor(sqrted);
  const powerd = pow(base, 2);
  const dcol = sqrted - base;
  const drow = ln - powerd;

  let col = base;
  let row = base;

  if (0 < dcol) col++;
  if (base < drow) row++;

  return { width: round(10000 / col) / 100 + "%", height: round(10000 / row) / 100 + "%" };
}
</script>

<template>
  <div class="containers" ref="mainRef">
    <div v-for="i in n" :id="'container' + i" :key="i" :style="style"></div>
  </div>
  <div class="toolbar">
    <input class="input-num" type="number" v-model.number="n" :min="1" />
    <button @click="create">
      <IconTree width="1em" height="1em" />
      生成
    </button>
    <button @click="destory">
      <IconPick width="1em" height="1em" />
      销毁
    </button>
    <button @click="snapshot">
      <IconShot width="1em" height="1em" />
      截图
    </button>
    <button @click="download">
      <IconDownload width="1em" height="1em" />
      下载
    </button>
  </div>
  <div class="information">
    <div>ellipsoid: {{ toMessage(state.cg1) }}</div>
    <div>terrain: {{ toMessage(state.cg2) }}</div>
    <div>scene: {{ toMessage(state.cg3) }}</div>
  </div>
  <div class="snapshot" v-show="!!state.data" @click="click2hide">
    <img :src="state.data" alt="截图" />
  </div>
</template>

<style lang="scss" scoped>
.containers {
  display: flex;
  position: relative;
  flex-wrap: wrap;
  width: 100%;
  height: 100%;

  :deep(.cesium-viewer) {
    .cesium-viewer-bottom {
      display: none;
    }
    .cesium-viewer-toolbar {
      display: none;
    }
    .cesium-viewer-animationContainer {
      display: none;
    }
    .cesium-viewer-timelineContainer {
      display: none;
    }
  }
}
.toolbar {
  display: flex;
  position: fixed;
  top: 0;
  left: 0;
  align-items: center;

  .input-num {
    width: 50px;
    height: 23px;
    text-align: center;
  }
}
.information {
  position: fixed;
  top: 0;
  right: 0;
  color: white;
  font-size: 14px;
  font-family: "Microsoft Yahei Mono";

  div {
    text-align: right;
  }
}
.snapshot {
  display: flex;
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
  justify-content: center;
  align-items: center;
  z-index: 10;
  background-color: #00000080;

  img {
    box-shadow: 0 0 5px white;
  }
}
</style>
